﻿// *---------------------------------------------------------------------------------------------------------*
// Solution:         JwikIDE									
// Project:           JwiKIDE								
// Source File:    ClassInfo.cs									
// *---------------------------------------------------------------------------------------------------------*
// 
// © 2012 Digital Six Labs, LLC All Rights Reserved
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
// and associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// 
// 
// 
// 
namespace JwiKIDE.JavaClassFileClasses
{
    using System;
    using System.Collections.Generic;
    using System.IO;

    using JwiKIDE.Utilities;

    

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class ClassInfo : CommonInfo
    {
        #region Constructors and Destructors

        public ClassInfo()
        {
            this.Fields = new List<FieldInfo>();
            this.Methods = new List<MethodInfo>();
        }

        #endregion

        #region Public Properties

        public short ClassIndex { get; private set; }

        public string ClassName { get; private set; }

        public ConstantPool ConstantPool { get; private set; }

        public List<FieldInfo> Fields { get; private set; }

        public short Flags { get; private set; }

        public InnerClassInfo[] InnerClasses { get; private set; }

        public Int32 MagicNumber { get; private set; }

        public Int16 MajorVersion { get; private set; }

        public List<MethodInfo> Methods { get; private set; }

        public Int16 MinorVersion { get; private set; }

        public string SourceFile { get; private set; }

        public short SuperClassIndex { get; private set; }

        public string SuperClassName { get; private set; }

        #endregion

        #region Public Methods and Operators

        public void LoadClassFile(string fileName)
        {
            using (var fs = new FileStream(fileName, FileMode.Open))
            {
                var reader = new BigEndianBinaryReader(fs);

                try
                {
                    this.MagicNumber = reader.ReadInt32();
                    if ((UInt32)this.MagicNumber != 0xCAFEBABE)
                    {
                        throw new IOException(
                            "Invalid magic number in class file.  Expected 0xCAFEBABE and got "
                            + String.Format("{0:x}", this.MagicNumber));
                    }
                    this.MajorVersion = reader.ReadInt16();
                    this.MinorVersion = reader.ReadInt16();

                    this.ConstantPool = new ConstantPool();
                    this.ConstantPool.ReadFromFile(reader);

                    this.Flags = reader.ReadInt16();
                    this.ClassIndex = reader.ReadInt16();
                    this.SuperClassIndex = reader.ReadInt16();

                    this.ClassName = this.ConstantPool.ConstantPoolEntries[ConstantPool.ConstantPoolEntries[this.ClassIndex].Index1].StringValue;
                    this.SuperClassName = this.ConstantPool.ConstantPoolEntries[ConstantPool.ConstantPoolEntries[this.SuperClassIndex].Index1].StringValue;

                    if (reader.ReadInt16() > 0)
                    {
                        throw new IOException("Interfaces are not supported");
                    }
                    int fieldCount = reader.ReadInt16();
                    for (int i = 0; i < fieldCount; i++)
                    {
                        short accessFlags = reader.ReadInt16();
                        short nameIndex = reader.ReadInt16();
                        short signatureIndex = reader.ReadInt16();

                        var newFieldInfo = new FieldInfo
                            {
                                AccessFlags = accessFlags,
                                Signature = this.ConstantPool.ConstantPoolEntries[signatureIndex].StringValue,
                                Name = this.ConstantPool.ConstantPoolEntries[nameIndex].StringValue
                            };
                        this.Fields.Add(newFieldInfo);
                        short fieldAttrCount = reader.ReadInt16();
                        for (int i1 = 0; i1 < fieldAttrCount; i1++)
                        {
                            short nI = reader.ReadInt16();
                            Int32 len = reader.ReadInt32();
                            var bytes = reader.ReadBytes(len);
                            var byteReader = new BigEndianBinaryReader(new MemoryStream(bytes));
                            string nm = ConstantPool.ConstantPoolEntries[nI].StringValue;
                            switch (nm)
                            {
                                case "ConstantValue":
                                    short cvIndex = byteReader.ReadInt16();
                                    newFieldInfo.ConstantValue = ConstantPool.ConstantPoolEntries[cvIndex].IntValue;
                                    break;
                                case "Synthetic":
                                    newFieldInfo.Synthetic = true;
                                    break;
                                default:
                                    byte[] d1 = byteReader.ReadBytes(len);
                                    var ai1 = new AttributeInfo { Name = nm, Data = d1 };
                                    newFieldInfo.Attributes.Add(ai1);
                                    break;

                            }
                        }
                    }
                    int methodCount = reader.ReadInt16();
                    for (int i = 0; i < methodCount; i++)
                    {
                        short accessFlags = reader.ReadInt16();
                        short nameIndex = reader.ReadInt16();
                        short signatureIndex = reader.ReadInt16();

                        var newMethodInfo = new MethodInfo
                            {
                                AccessFlags = accessFlags,
                                Signature = this.ConstantPool.ConstantPoolEntries[signatureIndex].StringValue,
                                Name = this.ConstantPool.ConstantPoolEntries[nameIndex].StringValue
                            };

                        short methodAttributeCount = reader.ReadInt16();
                        for (int i1 = 0; i1 < methodAttributeCount;i1++ )
                        {
                            short nI = reader.ReadInt16();
                            Int32 len = reader.ReadInt32();
                            var bytes = reader.ReadBytes(len);
                            var byteReader = new BigEndianBinaryReader(new MemoryStream(bytes));
                            string nm = ConstantPool.ConstantPoolEntries[nI].StringValue;
                            switch(nm)
                            {
                                case "Exceptions":
                                    short count = byteReader.ReadInt16();
                                    for (int a = 0; a < count;a++)
                                    {
                                        short exceptionClassIndex = byteReader.ReadInt16();
                                        Int32 exceptionClassNameIndex =
                                            ConstantPool.ConstantPoolEntries[exceptionClassIndex].Index1;
                                        string exceptionName =
                                            ConstantPool.ConstantPoolEntries[exceptionClassNameIndex].StringValue;
                                        newMethodInfo.Exceptions.Add(exceptionName);
                                    }
                                    break;
                                case "Code":
                                    var maxStack = byteReader.ReadInt16();
                                    var maxLocals = byteReader.ReadInt16();
                                    var bclen = byteReader.ReadInt32();
                                    var byteCode = byteReader.ReadBytes(bclen);
                                    var exceptionTable = new ExceptionInfo[byteReader.ReadInt16()];
                                    for(int ei=0; ei<exceptionTable.Length;i++)
                                    {
                                        var startPC = byteReader.ReadInt16();
                                        var endPC = byteReader.ReadInt16();
                                        var handlerPC = byteReader.ReadInt16();
                                        var catchTypeIndex = byteReader.ReadInt16();

                                        string catchTypeName = string.Empty;
                                        
                                        if(catchTypeIndex!=0)
                                        {
                                            int catchTypeNameIndex =
                                                ConstantPool.ConstantPoolEntries[catchTypeIndex].Index1;
                                            catchTypeName =
                                                ConstantPool.ConstantPoolEntries[catchTypeNameIndex].StringValue;
                                        }

                                        exceptionTable[i] = new ExceptionInfo
                                            {
                                                StartPC = startPC,
                                                EndPC = endPC,
                                                HandlerPC = handlerPC,
                                                CatchType = catchTypeName
                                            };
                                    }
                                    var ci = new CodeInfo
                                        {
                                            MaxStack = maxStack,
                                            MaxLocals = maxLocals,
                                            ByteCode = byteCode,
                                            ExceptionTable = exceptionTable
                                        };
                                    newMethodInfo.CodeInfo = ci;
                                    var codeAttributeCount = byteReader.ReadInt16();
                                    for (int j = 0; j < codeAttributeCount;j++ )
                                    {
                                        var ni = byteReader.ReadInt16();
                                        var l = byteReader.ReadInt32();
                                        var caBytes = byteReader.ReadBytes(l);
                                        var caReader = new BigEndianBinaryReader(new MemoryStream(caBytes));

                                        var caname = ConstantPool.ConstantPoolEntries[ni].StringValue;
                                        switch(caname)
                                        {
                                            case "LineNumberTable":
                                                var tbl = new LineNumberInfo[caReader.ReadInt16()];
                                                for (int l1 = 0; l1 < tbl.Length;l1++ )
                                                {
                                                    short startPC = caReader.ReadInt16();
                                                    short lineNumber = caReader.ReadInt16();
                                                    tbl[l1] = new LineNumberInfo
                                                        { StartPC = startPC, LineNumber = lineNumber };
                                                }
                                                ci.LineNumberTable = tbl;
                                                break;
                                            case "LocalVariableTable":
                                                var lvtbl = new LocalVariableInfo[caReader.ReadInt16()];
                                                for(int l2=0;l2<lvtbl.Length;l2++)
                                                {
                                                    short startPC = caReader.ReadInt16();
                                                    short length = caReader.ReadInt16();
                                                    short nmIndex = caReader.ReadInt16();
                                                    short sigIndex = caReader.ReadInt16();
                                                    short slot = caReader.ReadInt16();
                                                    string name =
                                                        ConstantPool.ConstantPoolEntries[nmIndex].StringValue;
                                                    string sig =
                                                        ConstantPool.ConstantPoolEntries[sigIndex].StringValue;
                                                    lvtbl[l2] = new LocalVariableInfo
                                                        {
                                                            Length = length,
                                                            StartPC = startPC,
                                                            Name = name,
                                                            Signature = sig,
                                                            Slot = slot
                                                        };
                                                }
                                                ci.LocalVariableTable = lvtbl;
                                                break;
                                            default:
                                                byte[] d = caReader.ReadBytes(l);
                                                var ai = new AttributeInfo { Name = caname, Data = d };
                                                ci.Attributes.Add(ai);
                                                break;
                                        }
                                    }
                                    break;
                                case "Deprecated":
                                    newMethodInfo.Deprecated = true;
                                    break;
                                default:
                                    byte[] d1 = byteReader.ReadBytes(len);
                                    var ai1 = new AttributeInfo { Name = nm, Data = d1 };
                                    newMethodInfo.Attributes.Add(ai1);
                                    break;
                            }

                        }

                        this.Methods.Add(newMethodInfo);
                    }
                    int attributeCount = reader.ReadInt16();
                    for (int i = 0; i < attributeCount; i++)
                    {
                        short nameIndex = reader.ReadInt16();
                        int length = reader.ReadInt32();
                        var bytes = new byte[length];
                        bytes = reader.ReadBytes(length);

                        var byteReader = new BigEndianBinaryReader(new MemoryStream(bytes));

                        string name = this.ConstantPool.ConstantPoolEntries[nameIndex].StringValue;
                        switch (name)
                        {
                            case "SourceFile":
                                Int16 fileNameIndex = byteReader.ReadInt16();
                                this.SourceFile = this.ConstantPool.ConstantPoolEntries[fileNameIndex].StringValue;
                                break;
                            case "InnerClasses":
                                Int16 rows = byteReader.ReadInt16();
                                var innerClasses = new InnerClassInfo[rows];
                                for (int i1 = 0; i1 < rows; i1++)
                                {
                                    Int16 innerClassIndex = byteReader.ReadInt16();
                                    Int16 outerClassIndex = byteReader.ReadInt16();
                                    Int16 simpleNameIndex = byteReader.ReadInt16();
                                    Int16 flags = byteReader.ReadInt16();

                                    Int32 innerClassNameIndex =
                                        this.ConstantPool.ConstantPoolEntries[innerClassIndex].Index1;
                                    string innerClassName =
                                        this.ConstantPool.ConstantPoolEntries[innerClassNameIndex].StringValue;
                                    string outerClassName = string.Empty;
                                    if (outerClassIndex != 0)
                                    {
                                        Int32 outerClassNameIndex =
                                            this.ConstantPool.ConstantPoolEntries[outerClassIndex].Index1;
                                        outerClassName =
                                            this.ConstantPool.ConstantPoolEntries[outerClassNameIndex].StringValue;
                                    }
                                    string simpleName = string.Empty;
                                    if (simpleNameIndex != 0)
                                    {
                                        simpleName = this.ConstantPool.ConstantPoolEntries[simpleNameIndex].StringValue;
                                    }

                                    var newInfo = new InnerClassInfo
                                        {
                                            Flags = flags,
                                            InnerClass = innerClassName,
                                            OuterClass = outerClassName,
                                            SimpleName = simpleName
                                        };

                                    innerClasses[i1] = newInfo;
                                }
                                this.InnerClasses = innerClasses;
                                break;
                            default:
                                var dat = byteReader.ReadBytes(length);
                                var ai = new AttributeInfo { Data = dat, Name = name };
                                this.Attributes.Add(ai);
                                break;
                        }
                    }
                }
                catch (Exception)
                {
                    throw;
                }
            }
        }

        #endregion

        internal short GetMethodIndex(string name, string sig)
        {
            for(short i=0; i<Methods.Count;i++)
            {
                if (Methods[i].Name == name
                    && Methods[i].Signature == sig) return i;
            }
            return -1;
        }

        public ushort StaticFieldCount
        {
            get
            {
                ushort sum = 0;
                foreach (FieldInfo field in Fields)
                    if ((field.AccessFlags & 0x08) > 0) sum++;
                return sum;

            }
        }

        public ushort TotalNonStaticFields
        {
            get
            {
                ushort sum = 0;
                foreach (FieldInfo field in Fields) if ((field.AccessFlags & 0x08) ==0) sum++;
                int lowestNativeId = int.MaxValue;
                foreach(NativeFile nf in ConfigFile.NativeFiles)
                {
                    if (nf.LowestNativeClassId < lowestNativeId) lowestNativeId = nf.LowestNativeClassId;
                }
                    // add fields of super classes (only for non-native classes)
                if(GetSuperClassIndex() < lowestNativeId) 
                    sum += ClassLoader.Classes[this.GetSuperClassIndex()].TotalNonStaticFields;
                return sum;

            }
        }

        internal int GetSuperClassIndex()
        {
            int index = ClassLoader.GetClassIndex(GetSuperClassName());//getSuperClassName());

            // no such class in the local classes, try native
            if (index < 0)
                index = ConfigFile.GetNativeClassId(this.GetSuperClassName()); //NativeMapper.getNativeClassId(getSuperClassName());

            return index;
        }

        private string GetSuperClassName()
        {
            return SuperClassName;
        }

        internal bool ProvidesField(string fieldName, string fieldType)
        {
            for (int i = 0; i < Fields.Count; i++)
            {
                FieldInfo fieldInfo = (FieldInfo)Fields[i];

                if (fieldInfo.Name==fieldName)
                {
                    if (fieldType == null) return true;

                    if (fieldInfo.Signature==fieldType)
                        return true;
                }
            }
            return false;
        }

        internal FieldInfo GetFieldInfo(string name, string type)
        {
            for (int i = 0; i < Fields.Count; i++)
            {
                FieldInfo fieldInfo = this.Fields[i];

                if (fieldInfo.Name==name)
                {
                    if (type == null) return fieldInfo;

                    if (fieldInfo.Signature == type)
                        return fieldInfo;
                }
            }
            return null;
        }

        internal int GetFieldIndex(AccessTypeFlags accessTypeFlags, string name, string type)
        {
            int cnt = 0;

            for (int i = 0; i < Fields.Count; i++)
            {
                FieldInfo fieldInfo = Fields[i];

                if (((AccessTypeFlags)Fields[i].AccessFlags & AccessTypeFlags.STATIC) == accessTypeFlags)
                {
                    if ((fieldInfo.Name==name) 
                        && (fieldInfo.Signature==type))
                        return cnt;

                    cnt++;
                }
            }
            return -1;
        }
    }
}